﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace QQ2564874169.Miniblink
{
	public partial class MiniblinkBrowser : UserControl, IMiniblink
	{
		#region 属性

		public string LocalDomain { get; private set; }
		public string LocalResourceDir { get; private set; }

		[Browsable(false)]
		public string Url => MBApi.wkeGetURL(_mb).ToUTF8String() ?? string.Empty;

		[Browsable(false)]
		public bool IsDocumentReady => MBApi.wkeIsDocumentReady(_mb) != 0;

		[Browsable(false)]
		public string DocumentTitle => MBApi.wkeGetTitle(_mb).ToUTF8String() ?? string.Empty;

		[Browsable(false)]
		public int DocumentWidth => MBApi.wkeGetWidth(_mb);

		[Browsable(false)]
		public int DocumentHeight => MBApi.wkeGetHeight(_mb);

		[Browsable(false)]
		public int ContentWidth => MBApi.wkeGetContentWidth(_mb);

		[Browsable(false)]
		public int ContentHeight => MBApi.wkeGetContentHeight(_mb);

		[Browsable(false)]
		public bool CanGoBack => MBApi.wkeCanGoBack(_mb) != 0;

		[Browsable(false)]
		public bool CanGoForward => MBApi.wkeCanGoForward(_mb) != 0;

		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public float Zoom
		{
			get { return DesignMode ? 0 : MBApi.wkeGetZoomFactor(_mb); }
			set
			{
				if (!DesignMode)
					MBApi.wkeSetZoomFactor(_mb, value);
			}
		}

		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool CookieEnabled
		{
			get { return DesignMode || MBApi.wkeIsCookieEnabled(_mb) != 0; }
			set
			{
				if (!DesignMode && !InvokeRequired)
					MBApi.wkeSetCookieEnabled(_mb, value);
			}
		}

		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string UserAgent
		{
			get { return DesignMode ? "" : MBApi.wkeGetUserAgent(_mb).ToUTF8String(); }
			set
			{
				if (!DesignMode)
					MBApi.wkeSetUserAgent(_mb, value);
			}
		}

		#endregion

		#region 事件

		public event WndMsgCallback WndMsg;

		private wkeURLChangedCallback _mkeUrlChanged;
		private EventHandler<UrlChangedEventArgs> _urlChanged;

		public event EventHandler<UrlChangedEventArgs> UrlChanged
		{
			add
			{
				if (_mkeUrlChanged == null)
				{
					MBApi.wkeOnURLChanged(_mb, _mkeUrlChanged = new wkeURLChangedCallback(OnUrlChanged), IntPtr.Zero);
				}

				_urlChanged += value;
			}
			remove
			{
				_urlChanged -= value;

				if (_urlChanged == null)
				{
					MBApi.wkeOnURLChanged(_mb, null, IntPtr.Zero);
				}
			}
		}

		protected virtual void OnUrlChanged(IntPtr mb, IntPtr param, IntPtr url)
		{
			_urlChanged?.Invoke(this, new UrlChangedEventArgs
			{
				Url = url.WKEToUTF8String()
			});
		}

		private wkeNavigationCallback _wkeNavigateBefore;
		private EventHandler<NavigateEventArgs> _navigateBefore;

		public event EventHandler<NavigateEventArgs> NavigateBefore
		{
			add
			{
				if (_wkeNavigateBefore == null)
				{
					MBApi.wkeOnNavigation(_mb, _wkeNavigateBefore = new wkeNavigationCallback(OnNavigateBefore),
						IntPtr.Zero);
				}

				_navigateBefore += value;
			}
			remove
			{
				_navigateBefore -= value;

				if (_navigateBefore == null)
				{
					MBApi.wkeOnNavigation(_mb, null, IntPtr.Zero);
				}
			}
		}

		protected virtual byte OnNavigateBefore(IntPtr mb, IntPtr param, wkeNavigationType type, IntPtr url)
		{
			if (_navigateBefore == null)
				return 1;

			var e = new NavigateEventArgs
			{
				Url = url.WKEToUTF8String(),
				Type = type
			};
			_navigateBefore(this, e);

			return (byte) (e.Cancel ? 0 : 1);
		}

		private wkeDocumentReady2Callback _wkeDocumentReady;
		private EventHandler<DocumentReadyEventArgs> _documentReady;

		public event EventHandler<DocumentReadyEventArgs> DocumentReady
		{
			add
			{
				if (_wkeDocumentReady == null)
				{
					MBApi.wkeOnDocumentReady2(_mb, _wkeDocumentReady = new wkeDocumentReady2Callback(OnDocumentReady),
						IntPtr.Zero);
				}
				_documentReady += value;
			}
			remove
			{
				_documentReady -= value;

				if (_documentReady == null)
				{
					MBApi.wkeOnDocumentReady(_mb, null, IntPtr.Zero);
				}
			}
		}

		protected virtual void OnDocumentReady(IntPtr mb, IntPtr param, IntPtr isMain)
		{
			_documentReady?.Invoke(this, new DocumentReadyEventArgs() {IsMain = isMain.ToInt32() == 1});
		}

		private wkeConsoleCallback _wkeConsoleMessage;
		private EventHandler<ConsoleMessageEventArgs> _consoleMessage;

		public event EventHandler<ConsoleMessageEventArgs> ConsoleMessage
		{
			add
			{
				if (_wkeConsoleMessage == null)
				{
					MBApi.wkeOnConsole(_mb, _wkeConsoleMessage = new wkeConsoleCallback(OnConsoleMessage), IntPtr.Zero);
				}
				_consoleMessage += value;
			}
			remove
			{
				_consoleMessage -= value;

				if (_consoleMessage == null)
				{
					MBApi.wkeOnConsole(_mb, null, IntPtr.Zero);
				}
			}
		}

		protected virtual void OnConsoleMessage(IntPtr mb, IntPtr param, wkeConsoleLevel level, IntPtr message,
			IntPtr sourceName, uint sourceLine, IntPtr stackTrace)
		{
			_consoleMessage?.Invoke(this, new ConsoleMessageEventArgs
			{
				Level = level,
				Message = message.WKEToUTF8String(),
				SourceLine = (int) sourceLine,
				SourceName = sourceName.WKEToUTF8String(),
				StackTrace = stackTrace.WKEToUTF8String()
			});
		}

		private wkeNetResponseCallback _wkeNetResponse;
		private EventHandler<NetResponseEventArgs> _netResponse;

		public event EventHandler<NetResponseEventArgs> NetResponse
		{
			add
			{
				if (_wkeNetResponse == null)
				{
					MBApi.wkeNetOnResponse(_mb, _wkeNetResponse = new wkeNetResponseCallback(OnNetResponse),
						IntPtr.Zero);
				}
				_netResponse += value;
			}
			remove
			{
				_netResponse -= value;

				if (_netResponse == null)
				{
					MBApi.wkeNetOnResponse(_mb, null, IntPtr.Zero);
				}
			}
		}

		protected virtual bool OnNetResponse(IntPtr mb, IntPtr param, string url, IntPtr job)
		{
			if (_netResponse == null)
				return true;

			var e = new NetResponseEventArgs
			{
				Job = job,
				Url = url
			};
			_netResponse(this, e);

			if (e.Data != null)
			{
				NetSetData(job, e.Data, e.ContentType);
				return true;
			}

			return e.Cancel;
		}

		private wkeLoadUrlBeginCallback _wkeLoadUrlBegin;
		private wkeLoadUrlEndCallback _wkeLoadUrlEnd;
		private EventHandler<LoadUrlBeginEventArgs> _loadUrlBegin;

		public event EventHandler<LoadUrlBeginEventArgs> LoadUrlBegin
		{
			add
			{
				if (_wkeLoadUrlBegin == null)
				{
					MBApi.wkeOnLoadUrlBegin(_mb, _wkeLoadUrlBegin = new wkeLoadUrlBeginCallback(OnLoadUrlBegin),
						IntPtr.Zero);
					MBApi.wkeOnLoadUrlEnd(_mb, _wkeLoadUrlEnd = new wkeLoadUrlEndCallback(OnLoadUrlEnd), IntPtr.Zero);
				}
				_loadUrlBegin += value;
			}
			remove
			{
				_loadUrlBegin -= value;

				if (_loadUrlBegin == null)
				{
					MBApi.wkeOnLoadUrlBegin(_mb, null, IntPtr.Zero);
				}
			}
		}

		private void JobCompleted(NetJob job)
		{
			if (job.Data != null)
			{
				this.SafeInvoke(() =>
				{
					NetSetData(job.Handle, job.Data, job.ContentType);
					MBApi.wkeNetContinueJob(job.Handle);
				});
			}
			else
			{
				this.SafeInvoke(() =>
				{
					if (job.BeginArgs.HookRequest)
					{
						if (job.BeginArgs.IsLocalFile)
						{
							OnLoadUrlEnd(job.WebView, job.Handle);
						}
						else
						{
							MBApi.wkeNetHookRequest(job.Handle);
						}
					}
					MBApi.wkeNetContinueJob(job.Handle);
				});
			}
		}

		protected virtual bool OnLoadUrlBegin(IntPtr mb, IntPtr param, IntPtr url, IntPtr job)
		{
			if (_loadUrlBegin == null)
				return false;

			var rawurl = url.ToUTF8String();
			var e = new LoadUrlBeginEventArgs
			{
				Job = new NetJob(mb, job, JobCompleted) {Url = rawurl},
				Url = rawurl,
				RequestMethod = MBApi.wkeNetGetRequestMethod(job)
			};
			e.Job.BeginArgs = e;

			_loadUrlBegin(this, e);

			if (e.Job.IsAsync)
			{
				return false;
			}
			if (e.HookRequest)
			{
				if (e.IsLocalFile)
				{
					OnLoadUrlEnd(mb, job, e.Data);
					return true;
				}
				MBApi.wkeNetHookRequest(job);
				return false;
			}
			if (e.Data != null)
			{
				NetSetData(job, e.Data, e.ContentType);
				return true;
			}
			if (e.Cancel)
			{
				NetSetData(job);
			}
			return e.Cancel;
		}

		private void OnLoadUrlEnd(IntPtr mb, IntPtr param, IntPtr url, IntPtr job, IntPtr buf, int length)
		{
			var data = new byte[length];
			if (buf != IntPtr.Zero)
				Marshal.Copy(buf, data, 0, length);
			OnLoadUrlEnd(mb, job, data);
		}

		protected virtual void OnLoadUrlEnd(IntPtr webview, IntPtr job, byte[] data = null)
		{
			var begin = LoadUrlBeginEventArgs.GetByJob(job);
			if (begin == null || _wkeLoadUrlEnd == null) return;

			var end = begin.OnLoadUrlEnd(data);
			if (end.Modify)
			{
				NetSetData(job, end.Data, begin.ContentType);
			}
		}

		private static void NetSetData(IntPtr job, byte[] data = null, string mime = null)
		{
			if (data != null && data.Length > 0)
			{
				MBApi.wkeNetSetData(job, data, data.Length);
			}
			else
			{
				MBApi.wkeNetSetData(job, new byte[] {0}, 1);
			}
			if (mime != null)
			{
				MBApi.wkeNetSetMIMEType(job, mime);
			}
		}

		#endregion

		#region 公共方法

		public void ShowDevTools()
		{
			var path = Path.Combine(Application.StartupPath, "front_end", "inspector.html");
			MBApi.wkeShowDevtools(_mb, path, null, IntPtr.Zero);
		}

		public object RunJs(string script)
		{
			var es = MBApi.wkeGlobalExec(_mb);
			return MBApi.jsEvalW(es, script).ToValue(es);
		}

		public object CallJsFunc(string funcName, params object[] param)
		{
			var es = MBApi.wkeGlobalExec(_mb);
			var func = MBApi.jsGetGlobal(es, funcName);
			if (func == 0)
				throw new WKEFunctionNotFondException(funcName);
			var args = param.Select(i => i.ToJsValue(es)).ToArray();
			return MBApi.jsCall(es, func, MBApi.jsUndefined(), args, args.Length).ToValue(es);
		}

		public void BindNetFunc(NetFunc func)
		{
			var funcvalue = new wkeJsNativeFunction((es, state) =>
			{
				var handle = GCHandle.FromIntPtr(state);
				var nfunc = (NetFunc) handle.Target;
				var arglen = MBApi.jsArgCount(es);
				var args = new List<object>();
				for (var i = 0; i < arglen; i++)
				{
					args.Add(MBApi.jsArg(es, i).ToValue(es));
				}
				return nfunc.OnFunc(args.ToArray()).ToJsValue(es);
			});
			_keepref[func.Name] = func;

			var ptr = GCHandle.ToIntPtr(GCHandle.Alloc(func));

			MBApi.wkeJsBindFunction(func.Name, funcvalue, ptr, 0);
		}

		public void SetLocalResource(string dir, string domain)
		{
			LocalDomain = domain.TrimEnd('/');
			LocalResourceDir = dir.TrimEnd(Path.DirectorySeparatorChar);
		}

		public void SetHeadlessEnabled(bool enable)
		{
			MBApi.wkeSetHeadlessEnabled(_mb, enable);
		}

		public void SetNpapiPluginsEnable(bool enable)
		{
			MBApi.wkeSetNpapiPluginsEnabled(_mb, enable);
		}

		public void SetNavigationToNewWindow(bool enable)
		{
			MBApi.wkeSetNavigationToNewWindowEnable(_mb, enable);
		}

		public void SetCspCheckEnable(bool enable)
		{
			MBApi.wkeSetCspCheckEnable(_mb, enable);
		}

		public bool GoForward()
		{
			return MBApi.wkeGoForward(_mb) != 0;
		}

		public void EditorSelectAll()
		{
			MBApi.wkeEditorSelectAll(_mb);
		}

		public void EditorUnSelect()
		{
			MBApi.wkeEditorUnSelect(_mb);
		}

		public void EditorCopy()
		{
			MBApi.wkeEditorCopy(_mb);
		}

		public void EditorCut()
		{
			MBApi.wkeEditorCut(_mb);
		}

		public void EditorPaste()
		{
			MBApi.wkeEditorPaste(_mb);
		}

		public void EditorDelete()
		{
			MBApi.wkeEditorDelete(_mb);
		}

		public void EditorUndo()
		{
			MBApi.wkeEditorUndo(_mb);
		}

		public void EditorRedo()
		{
			MBApi.wkeEditorRedo(_mb);
		}

		public bool GoBack()
		{
			return MBApi.wkeGoBack(_mb) != 0;
		}

		public void SetProxy(WKEProxy proxy)
		{
			MBApi.wkeSetViewProxy(_mb, proxy);
		}

		public void LoadUri(string uri)
		{
			if (string.IsNullOrEmpty(uri?.Trim()))
				return;

			if (uri.SW("http:") || uri.SW("https:"))
			{
				MBApi.wkeLoadURL(_mb, uri);
			}
			else if (uri.StartsWith("/") && LocalDomain != null)
			{
				MBApi.wkeLoadURL(_mb, LocalDomain + uri);
			}
			else
			{
				MBApi.wkeLoadFileW(_mb, uri);
			}
		}

		public void LoadHtml(string html, string baseUrl = null)
		{
			if (baseUrl == null)
			{
				MBApi.wkeLoadHTML(_mb, html);
			}
			else
			{
				MBApi.wkeLoadHtmlWithBaseUrl(_mb, html, baseUrl);
			}
		}

		public void StopLoading()
		{
			MBApi.wkeStopLoading(_mb);
		}

		public void Reload()
		{
			MBApi.wkeReload(_mb);
		}

		#endregion

		public IntPtr MiniblinkHandle => _mb;
		private IntPtr _mb;
		private IntPtr _wndProc;
		private WndProcCallback _wndProcCallback;
		private wkePaintUpdatedCallback _paintUpdated;
		private Hashtable _keepref = new Hashtable();
		internal static MiniblinkBrowser InvokeBro { get; private set; }

		public MiniblinkBrowser()
		{
			InitializeComponent();

			_paintUpdated = new wkePaintUpdatedCallback(OnWKEPaintUpdated);
			_wndProcCallback = new WndProcCallback(OnWndProc);

			InvokeBro = InvokeBro ?? this;

			if (!Utils.IsDesignMode())
			{
				Init();

				LoadUrlBegin += HookLocalFileRequest;
			}
		}

		private void Init()
		{
			if (MBApi.wkeIsInitialize() == 0)
			{
				MBApi.wkeInitialize();
			}

			_mb = MBApi.wkeCreateWebView();

			if (_mb == IntPtr.Zero)
			{
				throw new WKECreateException();
			}

			MBApi.wkeSetHandle(_mb, Handle);
			MBApi.wkeOnPaintUpdated(_mb, _paintUpdated, Handle);
			MBApi.wkeResize(_mb, Width, Height);
			_wndProc = WinApi.SetWindowLongDelegate(Handle, (int) WinConst.GWL_WNDPROC, _wndProcCallback);
		}

		private IntPtr OnWndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam)
		{
			var result = WndMsg?.Invoke(hWnd, msg, wParam, lParam);
			if (result.HasValue)
				return result.Value;

			var wMsg = (WinConst) msg;

			switch (wMsg)
			{
				case WinConst.WM_PAINT:
				{
					var style = WinApi.GetWindowLong(hWnd, (int) WinConst.GWL_EXSTYLE);

					if ((int) WinConst.WS_EX_LAYERED != ((int) WinConst.WS_EX_LAYERED & style))
					{
						var ps = new WinPaint();
						var hdc = WinApi.BeginPaint(hWnd, ref ps);
						var rcClip = ps.rcPaint;
						var rcClient = ClientRectangle;
						var rcInvalid = rcClient;
						rcInvalid.Intersect(new Rectangle(rcClip.left, rcClip.top,
							rcClip.right - rcClip.left, rcClient.Bottom - rcClip.top));

						var srcX = rcInvalid.Left - rcClient.Left;
						var srcY = rcInvalid.Top - rcClient.Top;
						var destX = rcInvalid.Left;
						var destY = rcInvalid.Top;
						var width = rcInvalid.Right - rcInvalid.Left;
						var height = rcInvalid.Bottom - rcInvalid.Top;

						if (0 != width && 0 != height)
							WinApi.BitBlt(hdc, destX, destY, width, height, MBApi.wkeGetViewDC(_mb),
								srcX, srcY, (int) WinConst.SRCCOPY);

						WinApi.EndPaint(hWnd, ref ps);
						return IntPtr.Zero;
					}
					break;
				}
				case WinConst.WM_ERASEBKGND:
					return new IntPtr(1);
				case WinConst.WM_SIZE:
				{
					var width = lParam.ToInt32() & 65535;
					var height = lParam.ToInt32() >> 16;
					MBApi.wkeResize(_mb, width, height);
					break;
				}
				case WinConst.WM_KEYDOWN:
				{
					var code = wParam.ToInt32();
					uint flags = 0;
					if (((lParam.ToInt32() >> 16) & (int) WinConst.KF_REPEAT) != 0)
						flags |= (uint) wkeKeyFlags.WKE_REPEAT;
					if (((lParam.ToInt32() >> 16) & (int) WinConst.KF_EXTENDED) != 0)
						flags |= (uint) wkeKeyFlags.WKE_EXTENDED;

					if (MBApi.wkeFireKeyDownEvent(_mb, code, flags, false) != 0)
					{
						return IntPtr.Zero;
					}
					break;
				}
				case WinConst.WM_KEYUP:
				{
					var code = wParam.ToInt32();
					uint flags = 0;
					if (((lParam.ToInt32() >> 16) & (int) WinConst.KF_REPEAT) != 0)
						flags |= (uint) wkeKeyFlags.WKE_REPEAT;
					if (((lParam.ToInt32() >> 16) & (int) WinConst.KF_EXTENDED) != 0)
						flags |= (uint) wkeKeyFlags.WKE_EXTENDED;

					if (MBApi.wkeFireKeyUpEvent(_mb, code, flags, false) != 0)
					{
						return IntPtr.Zero;
					}
					break;
				}
				case WinConst.WM_CHAR:
				{
					var code = wParam.ToInt32();
					uint flags = 0;
					if (((lParam.ToInt32() >> 16) & (int) WinConst.KF_REPEAT) != 0)
						flags |= (uint) wkeKeyFlags.WKE_REPEAT;
					if (((lParam.ToInt32() >> 16) & (int) WinConst.KF_EXTENDED) != 0)
						flags |= (uint) wkeKeyFlags.WKE_EXTENDED;

					if (MBApi.wkeFireKeyPressEvent(_mb, code, flags, false) != 0)
					{
						return IntPtr.Zero;
					}
					break;
				}
				case WinConst.WM_LBUTTONDOWN:
				case WinConst.WM_MBUTTONDOWN:
				case WinConst.WM_RBUTTONDOWN:
				case WinConst.WM_LBUTTONDBLCLK:
				case WinConst.WM_MBUTTONDBLCLK:
				case WinConst.WM_RBUTTONDBLCLK:
				case WinConst.WM_LBUTTONUP:
				case WinConst.WM_MBUTTONUP:
				case WinConst.WM_RBUTTONUP:
				case WinConst.WM_MOUSEMOVE:
				{
					if (ContextMenuStrip != null)
					{
						if (wMsg == WinConst.WM_RBUTTONUP)
						{
							ContextMenuStrip.Show(MousePosition);
							break;
						}
					}
					switch (wMsg)
					{
						case WinConst.WM_LBUTTONDOWN:
						case WinConst.WM_MBUTTONDOWN:
						case WinConst.WM_RBUTTONDOWN:
							if (WinApi.GetFocus() != hWnd)
								WinApi.SetFocus(hWnd);
							WinApi.SetCapture(hWnd);
							break;
						case WinConst.WM_LBUTTONUP:
						case WinConst.WM_MBUTTONUP:
						case WinConst.WM_RBUTTONUP:
							WinApi.ReleaseCapture();
							break;
					}

					var x = Utils.LOWORD(lParam);
					var y = Utils.HIWORD(lParam);

					var flags = 0;

					if ((wParam.ToInt32() & (int) WinConst.MK_CONTROL) != 0)
						flags |= (int) wkeMouseFlags.WKE_CONTROL;
					if ((wParam.ToInt32() & (int) WinConst.MK_SHIFT) != 0)
						flags |= (int) wkeMouseFlags.WKE_SHIFT;

					if ((wParam.ToInt32() & (int) WinConst.MK_LBUTTON) != 0)
						flags |= (int) wkeMouseFlags.WKE_LBUTTON;
					if ((wParam.ToInt32() & (int) WinConst.MK_MBUTTON) != 0)
						flags |= (int) wkeMouseFlags.WKE_MBUTTON;
					if ((wParam.ToInt32() & (int) WinConst.MK_RBUTTON) != 0)
						flags |= (int) wkeMouseFlags.WKE_RBUTTON;

					if (MBApi.wkeFireMouseEvent(_mb, msg, x, y, flags) != 0)
					{
						return IntPtr.Zero;
					}
					break;
				}
				case WinConst.WM_CONTEXTMENU:
				{
					var x = Utils.LOWORD(lParam);
					var y = Utils.HIWORD(lParam);
					var point = PointToClient(new Point(x, y));

					uint flags = 0;

					if ((wParam.ToInt32() & (int) WinConst.MK_CONTROL) != 0)
						flags |= (uint) wkeMouseFlags.WKE_CONTROL;
					if ((wParam.ToInt32() & (int) WinConst.MK_SHIFT) != 0)
						flags |= (uint) wkeMouseFlags.WKE_SHIFT;

					if ((wParam.ToInt32() & (int) WinConst.MK_LBUTTON) != 0)
						flags |= (uint) wkeMouseFlags.WKE_LBUTTON;
					if ((wParam.ToInt32() & (int) WinConst.MK_MBUTTON) != 0)
						flags |= (uint) wkeMouseFlags.WKE_MBUTTON;
					if ((wParam.ToInt32() & (int) WinConst.MK_RBUTTON) != 0)
						flags |= (uint) wkeMouseFlags.WKE_RBUTTON;

					if (MBApi.wkeFireContextMenuEvent(_mb, point.X, point.Y, flags) != 0)
					{
						return IntPtr.Zero;
					}
					break;
				}
				case WinConst.WM_MOUSEWHEEL:
				{
					var x = Utils.LOWORD(lParam);
					var y = Utils.HIWORD(lParam);
					var delta = Utils.HIWORD(wParam);
					var point = PointToClient(new Point(x, y));

					uint flags = 0;

					if ((wParam.ToInt32() & (int) WinConst.MK_CONTROL) != 0)
						flags |= (uint) wkeMouseFlags.WKE_CONTROL;
					if ((wParam.ToInt32() & (int) WinConst.MK_SHIFT) != 0)
						flags |= (uint) wkeMouseFlags.WKE_SHIFT;

					if ((wParam.ToInt32() & (int) WinConst.MK_LBUTTON) != 0)
						flags |= (uint) wkeMouseFlags.WKE_LBUTTON;
					if ((wParam.ToInt32() & (int) WinConst.MK_MBUTTON) != 0)
						flags |= (uint) wkeMouseFlags.WKE_MBUTTON;
					if ((wParam.ToInt32() & (int) WinConst.MK_RBUTTON) != 0)
						flags |= (uint) wkeMouseFlags.WKE_RBUTTON;

					if (MBApi.wkeFireMouseWheelEvent(_mb, point.X, point.Y, delta, flags) != 0)
					{
						return IntPtr.Zero;
					}
					break;
				}
				case WinConst.WM_SETFOCUS:
					MBApi.wkeSetFocus(_mb);
					return IntPtr.Zero;
				case WinConst.WM_KILLFOCUS:
					MBApi.wkeKillFocus(_mb);
					return IntPtr.Zero;
				case WinConst.WM_SETCURSOR:
					if (MBApi.wkeFireWindowsMessage(_mb, hWnd, (uint) WinConst.WM_SETCURSOR, IntPtr.Zero, IntPtr.Zero,
						    IntPtr.Zero) != 0)
					{
						return IntPtr.Zero;
					}
					break;
				case WinConst.WM_IME_STARTCOMPOSITION:
				{
					var caret = MBApi.wkeGetCaretRect(_mb);
					var comp = new CompositionForm
					{
						dwStyle = (int) WinConst.CFS_POINT | (int) WinConst.CFS_FORCE_POSITION,
						ptCurrentPos =
						{
							x = caret.x,
							y = caret.y
						}
					};
					var imc = WinApi.ImmGetContext(hWnd);
					WinApi.ImmSetCompositionWindow(imc, ref comp);
					WinApi.ImmReleaseContext(hWnd, imc);
					return IntPtr.Zero;
				}
				case WinConst.WM_INPUTLANGCHANGE:
					return WinApi.DefWindowProc(hWnd, msg, wParam, lParam);
			}
			return WinApi.CallWindowProc(_wndProc, hWnd, msg, wParam, lParam);
		}

		private void OnWKEPaintUpdated(IntPtr mb, IntPtr param, IntPtr hdc, int x, int y, int w, int h)
		{
			if (IsDisposed) return;
			var rc = new WinRect(x, y, x + w, y + h);
			WinApi.InvalidateRect(Handle, ref rc, true);
		}

		public void RegisterNetFunc(object target)
		{
			var tg = target;
			var methods = tg.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

			foreach (var method in methods)
			{
				var attr = method.GetCustomAttribute<NetFuncAttribute>();
				if (attr == null) continue;
				BindNetFunc(new NetFunc(attr.Name ?? method.Name, ctx =>
				{
					var m = (MethodInfo) ctx.State;
					object ret;
					var mps = m.GetParameters();
					if (mps.Length < 1)
					{
						ret = m.Invoke(tg, null);
					}
					else
					{
						var param = ctx.Paramters;
						var mpvs = new object[mps.Length];
						for (var i = 0; i < mps.Length; i++)
						{
							var mp = mps[i];
							var v = param.Length > i ? param[i] : null;
							if (v != null)
							{
								var pt = mp.ParameterType;
								if (pt.IsGenericType)
								{
									if (pt.GetGenericTypeDefinition() == typeof(Nullable<>))
									{
										pt = pt.GetGenericArguments().First();
									}
								}
								if (pt == typeof(DateTime) && !(v is DateTime))
								{
									long l_date;
									if (long.TryParse(v.ToString(), out l_date))
									{
										v = l_date.ToDate();
									}
								}
								if (v is JsFunc)
								{
									mpvs[i] = v;
								}
								else
								{
									mpvs[i] = Convert.ChangeType(v, pt);
								}
							}
							else if (mp.ParameterType.IsValueType)
							{
								mpvs[i] = Activator.CreateInstance(mp.ParameterType);
							}
							else
							{
								mpvs[i] = null;
							}
						}
						ret = m.Invoke(tg, mpvs);
					}
					return ret;
				}, method));
			}
		}

		private void HookLocalFileRequest(object sender, LoadUrlBeginEventArgs e)
		{
			if (string.IsNullOrEmpty(LocalDomain))
				return;
			if (e.RequestMethod != wkeRequestType.Get)
				return;
			var url = e.Url;
			if (url.SW("http:") == false && url.SW("https:") == false)
				return;
			var uri = new Uri(url);
			if (string.Equals(uri.Host, LocalDomain, StringComparison.OrdinalIgnoreCase) == false)
				return;
			var path = uri.AbsolutePath;
			path = path.Replace("/", Path.DirectorySeparatorChar.ToString());
			path = LocalResourceDir + path;
			e.IsLocalFile = true;
			if (File.Exists(path))
			{
				e.Data = File.ReadAllBytes(path);
			}
		}

		public Image GetImage()
		{
			var w = MBApi.wkeGetContentWidth(_mb);
			var h = MBApi.wkeGetContentHeight(_mb);
			var bmp = new Bitmap(w, h);
			var rc = new Rectangle(0, 0, w, h);
			var data = bmp.LockBits(rc, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
			MBApi.wkePaint(_mb, data.Scan0, 0);
			bmp.UnlockBits(data);
			var bytes = new byte[w * h * 4];
			Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
			
			return bmp;
		}
	}
}
